﻿(*
 * Application entry point.
 *)

namespace PathFinder

open System
open System.IO
open System.Diagnostics
open System.Text.RegularExpressions

open PathFinder.Network
open PathFinder.NetlistParse
open PathFinder.NetworkDeploy
open PathFinder.NetworkSetup
open PathFinder.PathFindingAlgorithms.NetworkAlgorithmAStar

module Main =
    (* Constants *)
    ///
    /// Name of the application.
    ///
    [<Literal>]
    let public ApplicationName = "RouteSharp"
    ///
    /// Authors of the application.
    ///
    [<Literal>]
    let public ApplicationAuthors = "Davide G. Monaco; Andrea Tino"
    ///
    /// Version of the application.
    ///
    [<Literal>]
    let public ApplicationVersion = "1.2"
    ///
    /// Development dates of the application.
    ///
    [<Literal>]
    let public ApplicationDevelopmentDates = "2011"
    ///
    /// License of the application.
    ///
    [<Literal>]
    let public ApplicationLicensingSystem = "GNU General Public License v2.1"
    ///
    /// Name of the application.
    ///
    [<Literal>]
    let public ApplicationLicenseContent = "See @ http://routesharp.codeplex.com/license"
    ///
    /// Line header.
    ///
    let public ConsoleLineHeader = ApplicationName + " >> "
    ///
    /// Timing element.
    ///
    type public TimingElement = {
        NSecs: int64;
        Ticks: int64;
        }
    ///
    /// Collects timings.
    ///
    type public RuntimeTimings = {
        mutable ParseTime: TimingElement;
        mutable SetupTime: TimingElement;
        mutable DeployTime: TimingElement;
        mutable PathQueriesExecutionTimes: (string * string * TimingElement) list;
        mutable PathQueriesTotalExecutionTime: TimingElement
        }
    ///
    /// Modes of running.
    ///
    type public RunMode =
        | Verbose = 0
        | NonVerbose = 1
        | Interactive = 2
    (* Functions *)
    ///
    /// Prints timings.
    ///
    let public PrintTimingSummary (timings: RuntimeTimings) =
        printfn "******************"
        printfn "* Timing Summary *"
        printfn "******************"
        printfn "Parsing time: %f secs - %f msecs - %d nsecs - %d ticks" (double (timings.ParseTime.NSecs) / double (1000) / double (1000) / double (1000)) (double (timings.ParseTime.NSecs) / double (1000) / double (1000)) timings.ParseTime.NSecs timings.ParseTime.Ticks
        printfn "Setup time: %f secs - %f msecs - %d nsecs - %d ticks" (double (timings.SetupTime.NSecs) / double (1000) / double (1000) / double (1000)) (double (timings.SetupTime.NSecs) / double (1000) / double (1000)) timings.SetupTime.NSecs timings.SetupTime.Ticks
        printfn "Deploy time: %f secs - %f msecs - %d nsecs - %d ticks" (double (timings.DeployTime.NSecs) / double (1000) / double (1000) / double (1000)) (double (timings.DeployTime.NSecs) / double (1000) / double (1000)) timings.DeployTime.NSecs timings.DeployTime.Ticks
        printfn "Showing times for paths computetion:"
        for (query, path, times) in timings.PathQueriesExecutionTimes do
            printfn "- Query \"%s\" having path \"%s\": %f secs - %f msecs - %d nsecs - %d ticks" query path (double (times.NSecs) / double (1000) / double (1000) / double (1000)) (double (times.NSecs) / double (1000) / double (1000)) times.NSecs times.Ticks
        printfn "Total computation time for paths: %f secs - %f msecs - %d nsecs - %d ticks" (double (timings.PathQueriesTotalExecutionTime.NSecs) / double (1000) / double (1000) / double (1000)) (double (timings.PathQueriesTotalExecutionTime.NSecs) / double (1000) / double (1000)) timings.PathQueriesTotalExecutionTime.NSecs timings.PathQueriesTotalExecutionTime.Ticks
    ///
    /// Interaction with the user.
    ///
    let rec public MainInteractive (deployer: NetworkDeployment, timings: RuntimeTimings) =
        (* Catch the user input *)
        printfn "Insert query: "
        let line = Console.ReadLine ()
        if line = "exit" then
            printfn "About to exit... but first, the timing report:"
            PrintTimingSummary timings
            printfn "Exiting due to user interactive request."
            0
        else
            (* Processing the line *)
            let rxptqr = new Regex (QueryDeclarationRegex) (* Regex for checking the presence of a query declaration token *)
            if (((rxptqr.Matches (line)).Count) >= 1) then (* Testing for query declaration *)
                (* A path query was provided *)
                let nodestart = (((((new Regex (NodeIdentifierRegex)).Matches (line)).[0].Value).Replace ("<", "")).Replace (">", "")) (* Getting first node id *)
                let nodeend = (((((new Regex (NodeIdentifierRegex)).Matches (line)).[1].Value).Replace ("<", "")).Replace (">", "")) (* Getting other node id *)
                let query = "From " + nodestart + " To " + nodeend
                printfn "Path query \"%s\" recognized! Processing query..." query
                (* Veryfing nodes existance *)
                let (nodestart_exists, nodeend_exists) = (deployer.[nodestart], deployer.[nodeend])
                match (nodestart_exists, nodeend_exists) with
                | (Some v, Some r) ->
                    (* Both recognized *)
                    (* Processing *)
                    let watcher = new Stopwatch () (* Timer *)
                    let nsec_per_tick = (1000L * 1000L * 1000L) / Stopwatch.Frequency (* Important constant *)
                    watcher.Start () (* Starts timer *)
                    let path_result = AStar deployer.[nodestart].Value deployer.[nodeend].Value
                    watcher.Stop () (* Stops timer *)
                    timings.PathQueriesExecutionTimes <- timings.PathQueriesExecutionTimes @ [(query, (PrintPath "" (RebuildPath [] (snd path_result) nodeend)), {NSecs = watcher.ElapsedTicks * nsec_per_tick; Ticks = watcher.ElapsedTicks})]
                    printfn "Query: \"%s\" executed in %f msecs - %d nsec (%d ticks) ==> Path: \"%s\"" query ((double (watcher.ElapsedTicks * nsec_per_tick)) / 1000.0 / 1000.0) (int (watcher.ElapsedTicks * nsec_per_tick)) (int (watcher.ElapsedTicks)) (PrintPath "" (RebuildPath [] (snd path_result) nodeend))
                    watcher.Reset () (* Resetting *)
                | (None, Some r) ->
                    (* From not recognized *)
                    printfn "Unrecognized node \"%s\"!" nodestart
                | (Some v, None) ->
                    (* To not recognized *)
                    printfn "Unrecognized node \"%s\"!" nodeend
                | _ ->
                    (* Strange situation *)
                    printfn "Unrecognized nodes \"%s\" and \"%s\"!" nodestart nodeend
            else
                (* Not recognized *)
                printfn "Unrecognized pattern \"%s\"" line
            (* Recursing *)
            (deployer, timings) |> MainInteractive
    ///
    /// Main body.
    ///
    let public MainBody (verb: RunMode, filename: string) =
        let file = filename
        printfn "Loading file %s..." file
        (* Checking file existence *)
        if not (File.Exists file) then
            (* File does not exist *)
            printfn "Error loading file! File %s not found! Application exiting due to error." file
            1 (* Exits due to error *)
        else
            (* File exists *)
            let runtime_timings = {
                ParseTime = {NSecs = 0L; Ticks = 0L};
                SetupTime = {NSecs = 0L; Ticks = 0L};
                DeployTime = {NSecs = 0L; Ticks = 0L};
                PathQueriesExecutionTimes = [];
                PathQueriesTotalExecutionTime = {NSecs = 0L; Ticks = 0L}
                }
            let watcher = new Stopwatch () (* Timer *)
            let nsec_per_tick = (1000L * 1000L * 1000L) / Stopwatch.Frequency (* Important constant *)
            (* Printing file *)
            printfn "File loaded!"
            if verb = RunMode.Verbose then
                printfn "Printing file"
                let lines = file |> GetFileLines
                for l in [0 .. lines.Length - 1] do
                    printfn "%d) %s" (l + 1) lines.[l]
            (* Parsing file *)
            printfn "Parsing file..."
            watcher.Start () (* Starts timer *)
            let parse_res = file |> ParseNetFile (* Parsing *)
            watcher.Stop () (* Stops timer *)
            runtime_timings.ParseTime <- {NSecs = watcher.ElapsedTicks * nsec_per_tick; Ticks = watcher.ElapsedTicks}
            watcher.Reset () (* Resets timer *)
            printfn "File parsing ended!"
            if verb = RunMode.Verbose then
                printfn "Listing results:"
                printfn "Log:"
                for i in [0 .. parse_res.CheckResult.Log.Length - 1] do
                    printfn "Log %d) %s" (i + 1) (parse_res.CheckResult.Log.[i] |> PrintParserLogEntry)
            printfn "Errors:"
            for i in [0 .. parse_res.CheckResult.Errors.Length - 1] do
                printfn "Err %d) %s" (i + 1) (parse_res.CheckResult.Errors.[i] |> PrintFileError)
            (* Checking for errors in parsing *)
            match parse_res.CheckResult.Correctness with
            | true -> 
                (* There were no errors during parsing *)
                printfn "No errors!"
                printfn "Parse overall result: OK"
                printfn "Nodes, Connections and PathQueries have been parsed and stored!"
                (* Printing stored elements *)
                if verb = RunMode.Verbose then
                    printfn "Listing %d node(s)..." parse_res.Nodes.Length
                    for i in parse_res.Nodes do
                        printfn "Node) %s at (%f, %f)" i.NodeId (fst i.NodeCoordinates) (snd i.NodeCoordinates)
                    printfn "Listing %d connection(s)..." parse_res.Connections.Length
                    for i in parse_res.Connections do
                        printfn "Connection) %s -> %s w(%f)" i.NodeFromId i.NodeToId i.ConnectionCost
                    printfn "Listing %d Path Query(ies)..." parse_res.PathQueries.Length
                    for i in parse_res.PathQueries do
                        printfn "Path Query) %s => %s" i.PathStartNode i.PathEndNode
                (* Are there path queries? (Mode is not interactive) *)
                if ((parse_res.PathQueries.Length > 0) && not (verb = RunMode.Interactive)) || (verb = RunMode.Interactive) then
                    (* There are queries *)
                    (* Analyzing file *)
                    printfn "Analyzing file --> Analyzing connections..."
                    let incorrect_connections = AnalyzeConnectionsAfterParse parse_res.Nodes parse_res.Connections []
                    for i in [0 .. incorrect_connections.Length - 1] do
                        printfn "Inc %d) Invalid connection from %s to %s with %f. One or both nodes undefined!" (i + 1) incorrect_connections.[i].NodeFromId incorrect_connections.[i].NodeToId incorrect_connections.[i].ConnectionCost
                    (* Are there incorrect connections? *)
                    match incorrect_connections with
                    | [] ->
                        (* No incorrect connections *)
                        printfn "No invalid connections!"
                        (* Looking for queries *)
                        printfn "Analyzing file --> Analyzing path queries..."
                        let incorrect_pathqueries = AnalyzePathQueriesAfterParse parse_res.Nodes parse_res.PathQueries []
                        for i in [0 .. incorrect_pathqueries.Length - 1] do
                            printfn "Inc %d) Invalid Path Query from %s to %s. One or both nodes undefined!" (i + 1) incorrect_pathqueries.[i].PathStartNode incorrect_pathqueries.[i].PathEndNode
                        (* Are there incorrect path queries? *)
                        match incorrect_pathqueries with
                        | [] ->
                            (* No incorrect path queries *)
                            printfn "No invalid path queries!"
                            (* Starting setup *)
                            printfn "Building Network Setup..."
                            let mutable setup_nodes = []
                            let mutable setup_connections = []
                            watcher.Start () (* Starts timer *)
                            for i in parse_res.Nodes do
                                setup_nodes <- setup_nodes @ [{Node = i.NodeId; Coordinates = (fst i.NodeCoordinates, snd i.NodeCoordinates)}]
                            for i in parse_res.Connections do
                                setup_connections <- setup_connections @ [{NodeFrom = i.NodeFromId; NodeTo = i.NodeToId; Cost = i.ConnectionCost}]
                            watcher.Stop () (* Stops timer *)
                            runtime_timings.SetupTime <- {NSecs = watcher.ElapsedTicks * nsec_per_tick; Ticks = watcher.ElapsedTicks}
                            watcher.Reset () (* Resets timer *)
                            printfn "Network Setup completed!"
                            (* Deployment *)
                            printfn "Deploying Network..."
                            watcher.Start () (* Starts timer *)
                            let deployer = NetworkDeployment () (* Deploying *)
                            deployer.Deploy (setup_nodes, setup_connections)
                            watcher.Stop () (* Stops timer *)
                            runtime_timings.DeployTime <- {NSecs = watcher.ElapsedTicks * nsec_per_tick; Ticks = watcher.ElapsedTicks}
                            watcher.Reset () (* Resets timer *)
                            printfn "Network deployed!"
                            if verb = RunMode.Verbose then
                                printfn "Showing deployment..."
                                for i in deployer.AllNodesIdentifiers do
                                    printfn "- %s" (deployer.[i].Value.ToString ())
                            (* Starting pathfinding only if I am not interactive *)
                            if (verb = RunMode.Interactive) then
                                (* Interactive *)
                                printfn "********************************"
                                printfn "* Entering interactive mode... *"
                                printfn "********************************"
                                (deployer, runtime_timings) |> MainInteractive (* Recursion *)
                            else
                                (* Not interactive *)
                                printfn "************************"
                                printfn "* Calculating paths... *"
                                printfn "************************"
                                let watcher_total = new Stopwatch () (* Timer *)
                                watcher_total.Start () (* Starting total timer *)
                                for i in parse_res.PathQueries do
                                    let query = "From " + i.PathStartNode + " To " + i.PathEndNode
                                    watcher.Start () (* Starts timer *)
                                    let path_result = AStar deployer.[i.PathStartNode].Value deployer.[i.PathEndNode].Value
                                    watcher.Stop () (* Stops timer *)
                                    runtime_timings.PathQueriesExecutionTimes <- runtime_timings.PathQueriesExecutionTimes @ [(query, (PrintPath "" (RebuildPath [] (snd path_result) i.PathEndNode)), {NSecs = watcher.ElapsedTicks * nsec_per_tick; Ticks = watcher.ElapsedTicks})]
                                    watcher.Reset () (* Resetting *)
                                watcher_total.Stop () (* Stopping total timer *)
                                runtime_timings.PathQueriesTotalExecutionTime <- {NSecs = watcher_total.ElapsedTicks * nsec_per_tick; Ticks = watcher_total.ElapsedTicks}
                                watcher_total.Reset () (* Resetting total timer *)
                                printfn "Showing path results..."
                                for i in runtime_timings.PathQueriesExecutionTimes do
                                    let (query, path, timing) = i
                                    printfn "Query: \"%s\" executed in %d nsec (%d ticks) ==> Path: \"%s\"" query (int (timing.NSecs)) (int (timing.Ticks)) path
                                PrintTimingSummary runtime_timings
                                printfn "Application has finished working!"
                                printfn "Now exiting."
                                0 (* Exits normally *)
                        | _ ->
                            (* There are incorrect queries *)
                            printfn "Invalid Path Queries found! Application exiting due to error."
                            1 (* Exits due to error *)
                    | _ ->
                        (* There are incorrect connections *)
                        printfn "Invalid connections found! Application exiting due to error."
                        1 (* Exits due to error *)
                else
                    (* No queries *)
                    printfn "No queries to be processed! Application exiting."
                    0 (* Exits normally *)
            | false -> 
                (* There were errors during parsing *)
                printfn "Parse overall result: Errors in parsing! Application exiting due to error."
                1 (* Exits due to error *)
    ///
    /// Main function.
    ///
    [<EntryPoint>]
    let public Main (args: string[]) =
        (* Greeting the user *)
        printfn "%s v%s by %s now started!" ApplicationName ApplicationVersion ApplicationAuthors
        printfn "Application licensed under %s" ApplicationLicensingSystem
        printfn "License content: %s" ApplicationLicenseContent
        (* Verifying parameters passed - usage is: RouteSharp.exe filename *)
        if (args.Length > 2) && (not (args.Length = 0)) then
            (* Wrong parameters *)
            printfn "Error executing %s! Usage: %s <filename> [verb|nonverb -> defaults to verb]. Application exiting due to error." ApplicationName ApplicationName
            1 (* Exits due to error *)
        else
            (* Correct arguments *)
            if args.Length = 1 then
                (* Verbose *)
                printfn "Entering Verbose (default) mode..."
                MainBody (RunMode.Verbose, args.[0])
            else
                (* Non verbose / verbose or interactive *)
                match args.[1] with
                | "verb" -> 
                    (* Verbose mode *)
                    printfn "Entering Verbose mode..."
                    MainBody (RunMode.Verbose, args.[0])
                | "nonverb" ->
                    (* Non verbose mode *)
                    printfn "Entering Non-Verbose mode..."
                    MainBody (RunMode.NonVerbose, args.[0])
                | "interact" ->
                    (* Interactive mode *)
                    printfn "Entering Interactive mode..."
                    MainBody (RunMode.Interactive, args.[0])
                | _ ->
                    (* Incorrect mode *)
                    printfn "Incorrect parameters!"
                    printfn "Error executing %s! Usage: %s <filename> [verb|nonverb|interact -> defaults to verb]. Application exiting due to error." ApplicationName ApplicationName
                    1
                
